﻿//--------------------------------------
// Copyright     : W.A.I.S. TECHNOLOGY 2003-2008 ; http://www.wais-fr.com ; email: infos@wais-fr.com
// Author        : Henri d'Orgeval
// Contributors  : 
// Last release  : 2007/11/09 (yy/mm/dd)
// Version       : 1.0.0.2
// Pattern       : "Real Time Trace"
// Internal name : RTTParamInfo (RealTimeTrace Parameter Info)
// Description   : this class is a wrapper to manage Parameter passed to methods at run time
//                 
// Framework     : v2.0.50727 and above
//
// Licence       : Microsoft Permissive Licence
// W.A.I.S provides this code in the hope that it will be useful, but WITHOUT ANY WARRANTY , either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.          
// This library is free software; you can redistribute it and/or modify it under the terms of the Microsoft Permissive Licence
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//---------------------------------------
using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;

namespace RealtimeTrace
{
    sealed internal class TraceParamInfo : Itraceable, ITraceParamInfo
    {
        
        #region Itraceable Members

        /// <summary>
        /// Use this method to initialize the RealTimeTrace.
        /// This method should be called within the class constructor(s) 
        /// if you want to override the default RealtimeTrace configuration.
        /// </summary>
        private void InitializeRealTimeTrace()
        {
            this.RealTimeTraceProvider = DataProviderType.None;
            this.PreferredDumpType = DumpType.XML;
            this.ErrorsMaxSize = 4096;
        }

        /// <summary>
        /// By default, all exceptions that are raised within the RealtimeTrace layer
        /// are not saved by the Data Storage provider.
        /// </summary>
        private DataProviderType _RealTimeTraceProvider = DataProviderType.None;
        /// <summary>
        /// Gets or Sets the Data Store provider for the RealTimeTrace.
        /// By default this property returns 'LogFile'
        /// </summary>
        public DataProviderType RealTimeTraceProvider
        {
            get
            {
                return this._RealTimeTraceProvider;
            }
            set
            {
                this._RealTimeTraceProvider = value;
            }
        }

        private DumpType _PreferredDumpType = DumpType.XML;
        public DumpType PreferredDumpType
        {
            get
            {
                return this._PreferredDumpType;
            }
            set
            {
                this._PreferredDumpType = value;
            }
        }

        /// <summary>
        /// Sets any internal error message that occured within the execution of the RealTimeTrace Layer.
        /// The error message can be read by the implementor through the LastError property.
        /// </summary>
        /// <returns></returns>
        private string InternalError
        {
            set
            {
                try
                {
                    if (System.String.IsNullOrEmpty(value))
                    {
                        return;
                    }
                    this._LastError = value;
                    this._Errors = value;
                    this._HasErrors = true;
                }
                catch (Exception )
                {
                    // this code should never run, but who knows ...
                }
            }
        }

        private bool _HasErrors = false;
        public bool HasErrors
        {
            get { return this._HasErrors; }
            set { this._HasErrors = value; }
        }

        private System.String _LastError = System.String.Empty;
        public string LastError
        {
            get { return this._LastError; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._LastError = value; 
            }
        }

        private System.String _Errors = System.String.Empty;
        public string Errors
        {
            get { return this._Errors; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._Errors = value;
            }
        }

        /// <summary>
        /// Clear any previous error messages hold by the property 'LastError'.
        /// The 'HasErrors' property is reset to false.
        /// Call this method at the beginning of all public methods in your class.
        /// </summary>
        private void ClearErrors()
        {
            try
            {
                this._Errors = System.String.Empty;
                this._LastError = System.String.Empty;
                this._HasErrors = false;
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// Add an error message to the current content of the 'Errors' Property.
        /// The 'HasErrors' property is set to true.
        /// </summary>
        /// <param name="errorMessage">Any message to be added to the Errors Property</param>
        /// <returns></returns>
        /// <remarks>Be carefull: by calling this method many times without calling the 'ClearErrors' in between,
        /// you might create a memory leak in your application.</remarks>
        private bool AddError(string errorMessage)
        {
            try
            {
                if (System.String.IsNullOrEmpty(errorMessage))
                {
                    return true;
                }

                this._HasErrors = true;

                // Update the LastError message
                this._LastError = errorMessage;

                // Update the Errors message
                this._Errors += errorMessage;
                this._Errors += "\r\n";

                // check for the constraint on this._Errors string length.
                // the purpose of this check is to avoid a possible memory leak.
                if (this._Errors.Length > this._ErrorsMaxSize)
                {
                    this._Errors = errorMessage;
                }

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Set the default maximum length of the Errors property to 4096 bytes.
        /// This threshold should prevent memory leak due to exceptions storms.
        /// </summary>
        private int _ErrorsMaxSize = 4096;
        /// <summary>
        /// Gets or Sets the maximum length in bytes for the 'Errors' property.
        /// Default value is 4096 bytes.
        /// </summary>
        private int ErrorsMaxSize
        {
            get
            {
                return this._ErrorsMaxSize;
            }
            set
            {

                if (value > 0)
                {
                    this._ErrorsMaxSize = value;
                }
            }
        }

        /// <summary>
        /// Log into the trace data store the error message.
        /// </summary>
        /// <param name="errorMessage">Any error message to be logged.</param>
        private void RealTimeTrace(string errorMessage)
        {
            try
            {
                if (System.String.IsNullOrEmpty(errorMessage))
                {
                    return;
                }

                if (!this.AddError(errorMessage))
                {
                    // something goes wrong ...     
                }
            }
            catch (Exception ex)
            {
                
               // this code should never run, but who knows ...
                this.InternalError = ex.Message;
            }

        }

        /// <summary>
        /// Log into the trace data store the current exception.
        /// </summary>
        /// <param name="ex">The current exception object to be analyzed and logged for further analysis.</param>
        private void RealTimeTrace(Exception ex)
        {
            try
            {
                if (ex == null)
                {
                    return;
                }

                if (!this.AddError(ex.Message))
                {
                    // something goes wrong ...     
                }
            }
            catch (Exception ex1)
            { 
                // this code should never run, but who knows ...
                this.InternalError = ex1.Message;
            }
        }

        /// <summary>
        /// Log into the trace data store the current exception.
        /// </summary>
        /// <param name="ex">The current exception object to be analyzed and logged.</param>
        /// <param name="args">Any number of objects that represent the calling parameters of the method in which an exception has been thrown.</param>
        private void RealTimeTrace(Exception ex, params object[] args)
        {
            try
            {
                if (ex == null)
                {
                    return;
                }

                if (!this.AddError(ex.Message))
                {
                    // something goes wrong ...     
                }
            }
            catch (Exception ex1)
            {
               // this code should never run, but who knows ...
                this.InternalError = ex1.Message;
            }
        }

        
        #endregion

        #region ITraceParamInfo Members

        private int _Position  = -1;
        /// <summary>
        /// Gets or Sets the position of the parameter within the method's signature.
        /// </summary>
        public int Position
        {
            get
            {
                return this._Position;
            }
            set
            {
                if (value < 0)
                {
                    return;
                }
                this._Position = value;
            }
        }

        private string _Name = System.String.Empty;
        /// <summary>
        /// Gets or Sets the name of the parameter.
        /// </summary>
        public string Name
        {
            get
            {
                return this._Name;
            }
            set
            {
                if (System.String.IsNullOrEmpty(value))
                {
                    return;
                }
                this._Name = value;
            }
        }

        private string _ParameterType = System.String.Empty;
        /// <summary>
        /// Gets or Sets the type of the parameter.
        /// </summary>
        public string ParameterType
        {
            get
            {
                return this._ParameterType;
            }
            set
            {
                if (System.String.IsNullOrEmpty(value))
                {
                    return;
                }
                this._ParameterType = value;
            }
        }

        private string _Value = "This object has not been passed to the RealtimeTrace framework";
        /// <summary>
        /// Gets or Sets the value of the parameter at execution time.
        /// </summary>
        public string Value
        {
            get
            {
                return this._Value;
            }
            set
            {
                if (System.String.IsNullOrEmpty(value))
                {
                    return;
                }
                this._Value = value;
            }
        }

        #region Details managment

        private Dictionary<string, object> _Details;

        /// <summary>
        /// Instanciate and initialize the internal dictionary object that holds any details on the parameter.
        /// </summary>
        /// <returns>Returns true if initialization succeeded, false otherwise.</returns>
        private bool InitDetails()
        {
            try
            {
                if (this._Details == null)
                {
                    _Details = new Dictionary<string, object>();
                }
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }

        /// <summary>
        /// Clears the internal dictionary object that holds any details on the parameter.
        /// </summary>
        /// <returns>Returns true if cleanup succeeded, false otherwise.</returns>
        private bool ClearDetails()
        {
            try
            {
                if (this._Details == null)
                {
                    return true;
                }

                if (this._Details.Count == 0)
                {
                    this._Details = null;
                    return true;
                }

                this._Details.Clear();
                this._Details = null;

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }

        }


        public Dictionary<string, object> Details
        {
            get
            {
                try
                {
                    // Clear any previous errors to avoid memory leak
                    this.ClearErrors();

                    if (this.InitDetails())
                    {
                        return this._Details;
                    }

                    return null;
                }
                catch (Exception ex)
                {
                    RealTimeTrace(ex);
                    return null;
                }
            }
            
        }

        /// <summary>
        /// Add a detailed info about the current parameter to the internal Dictionary.
        /// </summary>
        /// <param name="detailKey">Any string that uniquely identifies the detail.</param>
        /// <param name="value">The object that is associated with this detail.</param>
        /// <returns></returns>
        public bool AddDetails(string detailKey, object value)
        {
            try
            {
                // Clear any previous errors to avoid memory leak
                this.ClearErrors();

                if (System.String.IsNullOrEmpty(detailKey))
                {
                    throw new System.ArgumentNullException();
                }

                if (!this.InitDetails())
                {
                    throw new RealTimeTraceException(@"Cannot initialize internal collection");
                }

                this._Details.Add(detailKey, value);

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, detailKey, value);
                return false;
            }
        }

        #endregion

        
        private string _Dump = System.String.Empty;
        /// <summary>
        /// Gets or Sets a dump representation of the parameter.
        /// </summary>
        public string Dump
        {
            get
            {
                return this._Dump;
            }
            set
            {
                if (System.String.IsNullOrEmpty(value))
                {
                    return;
                }
                this._Dump = value;
            }
        }

        #endregion

        #region Automatic details extraction

        /// <summary>
        /// Gets any details that can be helpful for further analysis.
        /// </summary>
        /// <param name="value">Any object.</param>
        /// <returns>Returns true if execution has succeeded.</returns>
        private bool GetObjectDetails(System.Object value)
        {
            try
            {
                if (value == null)
                {
                    return true;
                }

                // Check if value is a FileSystemWatcher Object
                if (value is System.IO.FileSystemWatcher)
                {
                    System.IO.FileSystemWatcher myObj = (System.IO.FileSystemWatcher)value;
                    if (!this.GetObjectDetails(myObj))
                    {
                        return false;
                    }
                    return true;
                }

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, value);
                return false;
            }
        }

        /// <summary>
        /// Gets any details that can be helpful for further analysis.
        /// </summary>
        /// <param name="value">Any FileSystemWatcher object.</param>
        /// <returns>Returns true if execution has succeeded.</returns>
        private bool GetObjectDetails(System.IO.FileSystemWatcher value)
        {
            try
            {
                if (value == null)
                {
                    return true;
                }

                if (!this.AddDetails("Path", value.Path))
                {
                    throw new RealTimeTraceException("Cannot add details on Path property");
                }

                if (!this.AddDetails("Filter", value.Filter))
                {
                    throw new RealTimeTraceException("Cannot add details on Filter property");
                }

                if (!this.AddDetails("NotifyFilter", value.NotifyFilter))
                {
                    throw new RealTimeTraceException("Cannot add details on NotifyFilter property");
                }

                if (!this.AddDetails("IncludeSubdirectories", value.IncludeSubdirectories))
                {
                    throw new RealTimeTraceException("Cannot add details on IncludeSubdirectories property");
                }

                if (!this.AddDetails("InternalBufferSize", value.InternalBufferSize))
                {
                    throw new RealTimeTraceException("Cannot add details on InternalBufferSize property");
                }

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, value);
                return false;
            }
        }


        #endregion

        #region Dump methods

        /// <summary>
        /// Try to extract as much information as possible 
        /// from any object.
        /// </summary>
        /// <param name="value">Represents an object parameter passed to the method in which an exception has been raised.</param>
        /// <returns>Returns true if execution has succeeded.</returns>
        private bool GetObjectInfos(System.Object value)
        {
            try
            {
                // Get the value of the parameter
                if (value == null)
                {
                    this.Value = "null";
                    return true;
                }

                this.Value = value.ToString();

                // Get the type of the object
                this.ParameterType = value.GetType().ToString();

                // try to dump the parameter
                if (!this.GetObjectDump(value))
                {
                    // TODO : think of it for the next release
                }

                // try to get additional details about the parameter
                if (!this.GetObjectDetails(value))
                {
                    // TODO : think of it for the next release
                }

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, value);
                return false;
            }
        }

        /// <summary>
        /// Make a dump of the current parameter.
        /// </summary>
        /// <param name="value">Reference to the current parameter</param>
        /// <returns></returns>
        private bool GetObjectDump(System.Object value)
        {
            try
            {
                if (value == null)
                {
                    this.Dump = "null";
                    return true;
                }

                // Check if the parameter is a ValueType
                if (value is System.ValueType)
                {
                    return true;
                }

                // Check if the parameter is a string
                if (value is System.String)
                {
                    return true;
                }

                this.Dump = TraceFactory.ToDump(value);
                return true;
            }
            catch (Exception ex)
            {
                this.Dump = ex.Message;
                RealTimeTrace(ex, value);
                return false;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        ///  Create a new TraceParamInfo object,
        ///  and automatically populate the Position, Name and ParameterType properties from the ParameterInfo Object.
        /// </summary>
        /// <param name="p">ParameterInfo object extracted from the Exception object.
        /// This object represents one of the parameter passed to the method where the exception occured.
        /// </param>
        internal TraceParamInfo(System.Reflection.ParameterInfo p)
        {
            try
            {
                // Clear any previous errors to avoid memory leak
                this.ClearErrors();

                // Check input parameters
                if (p == null)
                {
                    return;
                }

                this.Position = p.Position;
                this.Name = p.Name;
                this.ParameterType = p.ParameterType.ToString();

            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, p);
            }
        }

        /// <summary>
        /// Create a new TraceParamInfo object, 
        /// automatically populate the Position, Name and ParameterType properties from the ParameterInfo Object.
        /// 
        /// </summary>
        /// <param name="p">ParameterInfo object extracted from the Exception object.
        /// This object represents one of the parameter passed to the method where the exception occured.</param>
        /// <param name="value">Any object that represents the parameter itself passed to the method where the exception occured.</param>
        internal TraceParamInfo(System.Reflection.ParameterInfo p, System.Object value)
        {
            try
            {
                // Clear any previous errors to avoid memory leak
                this.ClearErrors();

                // Check input parameters
                if (p == null)
                {
                    return;
                }

                this.Position = p.Position;
                this.Name = p.Name;
                this.ParameterType = p.ParameterType.ToString();
  
                // Get infos on the parameter object
                if (!GetObjectInfos(value))
                {
                    // TODO : think of it for the next release
                }

            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, p, value);
            }
        }

        /// <summary>
        /// Create a new TraceParamInfo object.
        /// </summary>
        /// <param name="paramName">The name of the parameter.
        /// This name should represent the name of a local variable that holds a reference to the object value.</param>
        /// <param name="value">Object reference that is hold by the variable named paramName.</param>
        internal TraceParamInfo(System.String paramName, System.Object value)
        {
            try
            {
                // Clear any previous errors to avoid memory leak
                this.ClearErrors();

                // Check input parameters
                if (System.String.IsNullOrEmpty(paramName))
                {
                    return;
                }

                this.Name = paramName;

                // Get infos on the parameter object
                if (!GetObjectInfos(value))
                {
                    // TODO : think of it for the next release
                }
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, paramName, value);
            }
        }

        
        #endregion
    }
}
